import { PokerHand } from "../constant/PokerHand";
import { PokerPoints } from "../constant/PokerPoints";
import { PokerSuits } from "../constant/PokerSuits";
import { IPokerVo, Poker } from "../data/Poker";

/**
 * 扑克牌工具类：判断牌组中的每张牌是否唯一、符合斗地主规则
 */
export default class PokerUtil {
  /**
   * 判断牌组中的每张牌是否唯一(花色+牌值)
   * @param cards 
   */
  public static isOnly(cards: Array<IPokerVo>): boolean {
    let set: Set<string> = new Set()
    for (let i = 0; i < cards.length; i++) {
      const cardStr = JSON.stringify(cards[i])
      if (set.has(cardStr)) {
        return false
      }
      set.add(cardStr)
    }

    return true
  }

  /**
   * c2是否存在c1中（允许多次包含——即cards2存在两个相同的值，而cards1只有一个，这也算匹配成功。如果要判断一组排是否某人的手牌需要要调用一次isOnly()）
   * @param c1 
   * @param c2 
   * @returns 
   */
  public static contain(cards1: Array<Poker>, cards2: Array<Poker>): boolean {
    if (cards1.length < cards2.length) {
      return false
    }

    let cardsSet = new Set<string>()
    cards1.forEach(card => {
      cardsSet.add(JSON.stringify(card))
    })

    for (let i = 0; i < cards2.length; i++) {
      const card = cards2[i]
      if (!cardsSet.has(JSON.stringify(card))) {
        return false
      }
    }

    return true
  }

  /**
   * 检测一副牌的牌型（hand会将cards牌组排序）
   * @param cards 
   */
  public static hand(cards: Array<IPokerVo>): PokerHand {
    // 所有牌是否唯一（安全校验）
    if (!this.isOnly(cards)) {
      return PokerHand.NONE
    }

    // 排序
    cards = this.sort(cards)

    const count = cards.length
    // 按牌数划分检测逻辑
    if (count === 1) {
      // 单张
      return PokerHand.T_A
    } else if (count === 2) {
      if (this.isSame(cards)) {
        // 对子
        return PokerHand.T_AA
      } else if (this.isRocket(cards)) {
        // 王炸（火箭）
        return PokerHand.T_ROCKET
      }
    } else if (count === 3) {
      if (this.isSame(cards)) {
        // 三张
        return PokerHand.T_AAA
      }
    } else if (count === 4) {
      if (this.isSame(cards)) {
        // 炸弹
        return PokerHand.T_AAAA
      }
      else if (this.isAAAB(cards)) {
        // 三带一
        return PokerHand.T_AAA_B
      }
    } else if (count >= 5) {
      if (this.isABCDE(cards)) {
        // 顺子
        return PokerHand.T_ABCDE
      } else if (this.isAAABB(cards)) {
        // 三带二，三带两对 AAA BC / AAA BBCC
        return PokerHand.T_AAA_BB
      } else if (this.isAABBCC(cards)) {
        // 双顺子 AABBCC
        return PokerHand.T_AABBCC
      } else if (this.isAAABBB(cards)) {
        // 三顺子(飞机) AAABBB
        return PokerHand.T_AAABBB
      }
      else if (this.isAAABBBCD(cards)) {
        // 飞机带翅膀 AAABBB CD / AAABBB CCDD
        return PokerHand.T_AAABBB_CD
      }
    }

    return PokerHand.NONE
  }

  /**
   * 判断cards2能否压cards1
   * @param cards1 
   * @param cards2
   */
  public static legal(cards1: Array<IPokerVo>, cards2: Array<IPokerVo>): boolean {
    // 判断牌组牌型，并且对牌组进行排序
    const hand1 = this.hand(cards1)
    const hand2 = this.hand(cards2)

    if (hand1 == PokerHand.T_ROCKET || hand2 == PokerHand.NONE) {
      // cards1是王炸，直接false
      // cards2没有牌型，直接false
      return false
    }

    //  && hand2 != PokerHand.NONE
    if (hand1 == PokerHand.NONE) {
      // cards1没有牌型，cards2有牌型（上一个if已确定） 直接true
      return true
    }

    if (hand1 === hand2 && cards1.length === cards2.length) {
      // 牌型相等、并且牌数相同
      switch (hand1) {
        // 单张
        case PokerHand.T_A:
        // 对子
        case PokerHand.T_AA:
        // 三张
        case PokerHand.T_AAA:
        // 炸弹
        case PokerHand.T_AAAA:
        // 顺子
        case PokerHand.T_ABCDE:
        // 双顺子
        case PokerHand.T_AABBCC:
        // 三顺子
        case PokerHand.T_AAABBB:
          // 比较最后一张点数权重的大小
          if (this.compareLastByPointWeight(cards1, cards2) < 0) {
            return true
          }
          break;
        // 飞机带翅膀
        case PokerHand.T_AAABBB_CD:
          const cards1By3 = this.getThreeSomeArr(cards1)
          const cards2By3 = this.getThreeSomeArr(cards2)
          // 比较最后一张点数权重的大小
          if (this.compareLastByPointWeight(cards1By3, cards2By3) < 0) {
            return true
          }
          break;
        // 三带一
        case PokerHand.T_AAA_B:
        // 三带二、三带两对
        case PokerHand.T_AAA_BB:
          const card1 = this.getThreeSome(cards1)
          const card2 = this.getThreeSome(cards2)
          if (!card1 || !card2) {
            throw new Error('no three are the same')
          }
          if (this.compareLastByPointWeight([card1], [card2]) < 0) {
            return true
          }
          break;
      }

    } else if (hand1 !== hand2) {
      // 牌型不相等；牌数可相等，也可不相等
      // cards2是王炸、炸弹，可以直接压
      if (hand2 === PokerHand.T_ROCKET || hand2 === PokerHand.T_AAAA) {
        return true
      }
    }

    // 牌型相等，但是牌数不相等，直接false
    return false
  }

  /**
   * 对一副牌从大到小排序
   * @param cards 
   * @returns 
   */
  public static sort(cards: Array<IPokerVo>) {
    return cards.sort((c1: IPokerVo, c2: IPokerVo) => {
      const poker1 = new Poker(c1)
      const poker2 = new Poker(c2)
      return poker1.compare(poker2)
    }).reverse()
  }

  /**
   * 牌组的点数是否都相等
   * @param cards 
   */
  public static isSame(cards: Array<IPokerVo>): boolean {
    let preCard = cards[0]
    for (let i = 1; i < cards.length; i++) {
      const currCard = cards[i]
      // 不一样 return false
      if (preCard.point != currCard.point) {
        return false
      }
    }
    return true
  }

  /**
   * 是否是王炸（火箭）
   * @param cards 
   * @returns 
   */
  public static isRocket(cards: Array<IPokerVo>): boolean {
    if (cards.length != 2) {
      return false
    }

    if (cards[0].suit !== PokerSuits.NONE || cards[1].suit !== PokerSuits.NONE) {
      return false
    }

    // 要么大王小王，要么小王大王
    if ((cards[0].point == PokerPoints.P_RJ && cards[1].point == PokerPoints.P_BJ) ||
      (cards[0].point == PokerPoints.P_BJ && cards[1].point == PokerPoints.P_RJ)) {
      return true
    }

    return false
  }

  /**
   * 是否是三带一（牌组必须排序后使用）
   */
  public static isAAAB(cards: Array<IPokerVo>): boolean {
    if (cards.length != 4) {
      return false
    }
    // 获取前3张
    let tempArr = cards.slice(0, cards.length - 1)
    if (this.isSame(tempArr)) {
      return true
    }

    // 获取后3张
    tempArr = cards.slice(1, cards.length)
    if (this.isSame(tempArr)) {
      return true
    }

    return false
  }

  /**
   * 是否是顺子（牌组必须大到小排序）
   * @param cards 
   */
  public static isABCDE(cards: Array<IPokerVo>): boolean {
    if (cards.length < 5) {
      return false
    }

    // 只能是3~A 的点数
    const pokerFirst = new Poker(cards[0])
    const pokerLast = new Poker(cards[cards.length - 1])
    if (!(3 <= pokerFirst.pointWeight && pokerFirst.pointWeight <= 14 &&
      3 <= pokerLast.pointWeight && pokerLast.pointWeight <= 14)) {
      return false
    }

    // 大到小的情况
    let i = 0
    for (i = 1; i < cards.length; i++) {
      const c1 = new Poker(cards[i - 1])
      const c2 = new Poker(cards[i])
      // 上一张牌不比当前的牌大1
      if (c1.pointWeight - c2.pointWeight !== 1) {
        return false
      }
    }

    return true
  }

  /**
   * 判断是否是三带一对、三带两对
   */
  public static isAAABB(cards: Array<IPokerVo>): boolean {
    // AAA BC、AAA BBCC
    if (cards.length != 5 && cards.length != 7) {
      return false
    }

    // 统计牌数
    let map: Map<PokerPoints, number> = new Map() //记牌器
    cards.forEach(card => {
      if (map.has(card.point)) {
        // count + 1
        const count = map.get(card.point)!
        map.set(card.point, count + 1)
      } else {
        // init
        map.set(card.point, 1)
      }
    })

    if (cards.length == 5) {
      // 三带一对判断
      let flags = [0, 0]  // 下标0:表示3对出现的次数，下标1:表示2对出现的次数
      for (let value of map.values()) {
        if (value == 3) {
          flags[0] += 1
        } else if (value == 2) {
          flags[1] += 1
        }
      }
      return flags[0] == 1 && flags[1] == 1

    } else if (cards.length == 7) {
      // 三带两对判断
      let flags = [0, 0] // 下标0:表示3对出现的次数，下标1:表示2对出现的次数
      for (let value of map.values()) {
        if (value == 3) {
          flags[0] += 1
        } else if (value == 2) {
          flags[1] += 1
        }
      }

      return flags[0] == 1 && flags[1] == 2
    }

    return false
  }

  /**
   * 双顺子、连对（必须大到小排序）
   * @param cards 
   * @returns 
   */
  public static isAABBCC(cards: Array<IPokerVo>): boolean {
    // AABBCC、AABBCCDD
    if (cards.length < 6 || cards.length % 2 !== 0) {
      // 小于6 或者 不是2的倍数，直接pass
      return false
    }

    // 只能是3~A 的点数
    const pokerFirst = new Poker(cards[0])
    const pokerLast = new Poker(cards[cards.length - 1])
    if (!(3 <= pokerFirst.pointWeight && pokerFirst.pointWeight <= 14 &&
      3 <= pokerLast.pointWeight && pokerLast.pointWeight <= 14)) {
      return false
    }

    //大到小
    // 0 1 2 3 4 5
    // 01 23 45
    // 6-2 = 4
    for (let i = 0; i < cards.length; i += 2) {
      const c1 = new Poker(cards[i])
      const c2 = new Poker(cards[i + 1])
      if (c1.point != c2.point) {
        // 不是成对关系，直接false
        return false
      }

      // 最后一组不需要和下一组比较差值
      if (i >= cards.length - 2) {
        break
      }

      // 和下一组比较差值
      const c3 = new Poker(cards[i + 2])
      if (c1.pointWeight - c3.pointWeight != 1) {
        return false
      }
    }

    return true
  }

  /**
   * 三顺子、飞机（必须大到小排序）
   * @param cards 
   * @returns 
   */
  public static isAAABBB(cards: Array<IPokerVo>): boolean {
    // AAABBB、AAABBBCCC、AAABBBCCCDDD

    // 小于6 或 不是3的倍数 直接pass
    if (cards.length < 6 || cards.length % 3 !== 0) {
      return false
    }

    // 只能是3~A 的点数
    const pokerFirst = new Poker(cards[0])
    const pokerLast = new Poker(cards[cards.length - 1])
    if (!(3 <= pokerFirst.pointWeight && pokerFirst.pointWeight <= 14 &&
      3 <= pokerLast.pointWeight && pokerLast.pointWeight <= 14)) {
      return false
    }

    // 三三匹配
    // 大到小情况
    for (let i = 0; i < cards.length; i += 3) {
      const c1 = cards[i]
      const c2 = cards[i + 1];
      const c3 = cards[i + 2];
      const cNext = cards[i + 3];

      if (c1.point !== c2.point || c2.point !== c3.point) {
        return false
      }

      // 最后一组不需要和下一组比较差值
      if (i >= cards.length - 3) {
        break
      }

      if (c1.point - cNext.point != 1) {
        return false
      }
    }

    return true
  }

  /**
   * 飞机带翅膀（必须排序）
   * @param cards 
   * @returns 
   */
  public static isAAABBBCD(cards: Array<IPokerVo>): boolean {
    // 组合方式：AAABBB-CD、AAABBB-CCDD、AAABBBCCC-DEF、AAABBBCCC-DDEEFF
    // 排序情况：AAABBB-CD、CD-AAABBB、C-AAABBB-D
    if (cards.length < 8) {
      return false
    }

    // map记牌 key = 牌值 value = array
    let cardMap: Map<PokerPoints, Array<IPokerVo>> = new Map()
    for (const card of cards) {
      if (cardMap.has(card.point)) {
        cardMap.get(card.point)?.push(card)
      } else {
        cardMap.set(card.point, [card])
      }
    }

    const cardsByCount3: Array<IPokerVo> = []
    let count3 = 0
    // 获取为3张的牌
    for (const cards of cardMap.values()) {
      if (cards.length == 3) {
        cardsByCount3.push(...cards)
        cardMap.delete(cards[0].point)
        count3++
      }
    }

    // console.log('cardMap:', cardMap)
    // console.log('cardByCount3:', cardByCount3)

    // 判断这三张是否是飞机
    if (this.isAAABBB(cardsByCount3) == false) {
      // console.log('not isAAABBB')
      return false
    }

    // 判断剩余的牌
    let cardCount = 0     // 剩下牌，第一组的数量
    let groupCount = 0    // 剩下的牌，还剩多少组
    for (const cards of cardMap.values()) {
      if (cardCount == 0) {
        cardCount = cards.length
      }

      // console.log('cards:', cards)
      // console.log('cardCount:', cardCount, 'card.length:', cards.length)
      if ((cardCount != 1 && cardCount != 2) || cardCount != cards.length) {
        return false
      }

      groupCount++
    }

    return count3 === groupCount
  }

  /**
   * 比较最后一张（比较点数的权重）
   */
  private static compareLastByPointWeight(cards1: Array<IPokerVo>, cards2: Array<IPokerVo>): number {
    const poker1 = new Poker(cards1[cards1.length - 1])
    const poker2 = new Poker(cards2[cards2.length - 1])
    return poker1.compareByPointWeight(poker2)
  }

  /**
   * 如果有三张相等，返回其中一张（牌组必须有序）
   * @param cards 
   * @returns 
   */
  private static getThreeSome(cards: Array<IPokerVo>): IPokerVo | undefined {
    for (let i = 0; i < cards.length - 2; i++) {
      const c1 = cards[i]
      const c2 = cards[i + 1]
      const c3 = cards[i + 2]
      if (c1.point === c2.point && c2.point === c3.point) {
        return c1
      }
    }
  }

  /**
   * 获取所有3张点数都相同牌（必须排序）
   * @param cards 
   */
  private static getThreeSomeArr(cards: Array<IPokerVo>): Array<IPokerVo> {
    // map记牌 key = 牌值 value = array
    let cardMap: Map<PokerPoints, Array<IPokerVo>> = new Map()
    for (const card of cards) {
      if (cardMap.has(card.point)) {
        cardMap.get(card.point)?.push(card)
      } else {
        cardMap.set(card.point, [card])
      }
    }

    const cardsByCount3: Array<IPokerVo> = []
    // 获取为3张的牌
    for (const cards of cardMap.values()) {
      if (cards.length == 3) {
        cardsByCount3.push(...cards)
        cardMap.delete(cards[0].point)
      }
    }

    return cardsByCount3
  }
}